import keyboard
from tools.beep import beep
import pynput
from tools import screen_width, screen_height
from tools.utils import mouse_move
from tools import get_key_state
from bdtime import tt
from bdtime import Time
from tools import vk
import threading
from collections import namedtuple
from tools import ms
import os
import json
from tools import kk
from tools.remove_temp_file import remove_path

MoveEvent = namedtuple('MoveEvent', ['x', 'y', 'time'])
ButtonEvent = namedtuple('ButtonEvent', ['x', 'y', 'press', 'button', 'time'])
WheelEvent = namedtuple('WheelEvent', ['x', 'y', 'delta', 'time'])


class AbsMouseRecorder:
    """
    鼠标录制类__绝对位置
    """
    def __init__(self, tt=None, is_abs=True):
        self.tt = tt if tt else Time()

        self.is_abs = is_abs

        self.recorded_mouse_events = []
        self.recorded_mouse_events.insert(0, {'recorder_type': self.__class__.__name__})

        self.is_recording = False
        self.listener = None

        self.save_dir = os.path.join(*['..', 'tempdir', 'recorded_states', 'abs_mouse'])

        self.stop_key = "alt + z"

    def on_move(self, x, y):
        if self.is_recording:
            e = MoveEvent(x, y, self.tt.now())._asdict()
            e.update({'event_type': 'MoveEvent'})
            self.recorded_mouse_events.append(e)
        return self.wait()

    def on_scroll(self, x, y, _, delta):
        if self.is_recording:
            e = WheelEvent(x, y, delta, self.tt.now())._asdict()
            e.update({'event_type': 'WheelEvent'})
            self.recorded_mouse_events.append(e)
        return self.wait()

    def on_click(self, x, y, btn, pressed):
        if btn == pynput.mouse.Button.left:
            button = 'left'
        elif btn == pynput.mouse.Button.right:
            button = 'right'
        elif btn == pynput.mouse.Button.middle:
            button = 'middle'
        else:
            raise ValueError(f'btn错误? btn: {btn}')

        if self.is_recording:
            e = ButtonEvent(x, y, pressed, button, self.tt.now())._asdict()
            e.update({'event_type': 'ButtonEvent'})
            self.recorded_mouse_events.append(e)
        return self.wait()

    def wait(self, virtual_key: (None, str, int) = None):
        virtual_key = virtual_key if virtual_key else self.stop_key
        key_state = get_key_state(virtual_key)
        return not key_state

    def force_kill_listener(self, wait_time=3):
        t1 = Time()
        while t1.during(wait_time) and self.is_recording:
            t1.sleep(0.5)
        print('*** ont kill_listener')
        self.stop_record()

    def record(self, until='esc', force_kill_time=0):
        keyboard.add_hotkey(until, self.stop_record)

        self.is_recording = True

        if force_kill_time:
            task = threading.Thread(target=self.force_kill_listener, kwargs={'wait_time': force_kill_time})
            task.start()

        self.on_move(*ms.position)

        with pynput.mouse.Listener(on_move=self.on_move, on_click=self.on_click,
                                   on_scroll=self.on_scroll) as self.listener:
            self.listener.join()

        self.is_recording = False
        return self.recorded_mouse_events

    def stop_record(self):
        self.is_recording = False
        if self.listener:
            self.listener.stop()
            self.listener = None

    def play(self, recorded_mouse_events=None, speed=1, need_press_alt=False):
        if not recorded_mouse_events:
            recorded_mouse_events = self.recorded_mouse_events

        if need_press_alt:
            kk.press('alt')

        tt = Time()
        # last_move_t = tt.now()
        last_move_t = 0
        dx = dy = 0
        for event_i in recorded_mouse_events:
            if tt.stop(self.stop_key, raise_error=False) or tt.stop('alt + x', raise_error=False):
                print(f'*** stop {self.__class__.__name__}.play by user!')
                break

            time = event_i.get('time')
            if time is None:
                continue

            while tt.now() * speed < time:
                tt.sleep(0.001)
                continue

            event_type = event_i['event_type']

            if event_type == 'ButtonEvent':
                ms.position = event_i['x'], event_i['y']

                button = getattr(ms.buttons, event_i['button'])

                if event_i['press']:
                    ms.press(button)
                else:
                    ms.release(button)
            elif event_type == 'MoveEvent':
                x, y = event_i['x'], event_i['y']
                # current_move_t = event_i['time']
                # t_interval = current_move_t - last_move_t

                x0, y0 = ms.position
                dx, dy = x - x0, y - y0
                ms.move_r(dx, dy)
                tt.sleep(0.001)

                # interval = 0 if t_interval < 0.001 else t_interval
                # threshold = 10
                # if (abs(dx) < threshold) or not interval:
                #     ms.move_r(dx, dy)
                # else:
                #     ms.move_r(dx, dy, interval=interval)
                # _dx, _dy = x - x0, y - y0
                # dx += _dx
                # dy += _dy
                #
                # current_move_t = tt.now()
                # t_interval = current_move_t - last_move_t
                # # if t_interval < 1 / 60:
                # #     continue
                # last_move_t = current_move_t
                #
                # # dx = 10
                # print(t_interval, 'continue --- current_move_dxy:', dx, dy)
                #
                # # ms.position = x, y
                # # ms.move_to(x, y)
                #
                # threshold = 10
                # move_times = int(dx / threshold) + 1
                # # interval = 1 / 60 / move_times
                # # ms.move_to(dx, dy, interval=interval, times=move_times)
                # interval = 0 if t_interval < 0.001 else t_interval
                #
                # if (dx < threshold and dy < threshold) or not interval:
                #     ms.move_r(dx, dy)
                # else:
                #     # ms.move_r(dx, dy, interval=1 / 60)
                #     # ms.move_r(dx, dy, interval=1 / 60, times=move_times)
                #     ms.move_r(dx, dy, interval=t_interval, times=move_times)
                # tt.sleep(0.001)

                # threshold = 10
                # move_times = int(dx / threshold) + 1
                # interval = 1 / 60 / move_times
                # # _dx = int(dx / move_times)
                # # _dy = 0
                #
                # for i in range(move_times):
                #     _dx = 0
                #     ms.move_r(_dx, _dy)
                #     tt.sleep(interval)

                # if dx < threshold and dy < threshold:
                #     ms.move_r(dx, dy)
                # else:
                #     move_times = int(threshold / threshold)
                #     interval = 1 / 60 / move_times
                #     _dx, _dy = int(dx / move_times), int(dy / move_times)
                #     for i in range(move_times):
                #         ms.move_r(_dx, _dy)
                #         tt.sleep(interval)
                #         # from bdtime import tt
                #         # tt.__init__()
                #         # for i in range(10):
                #         #     tt.sleep(0.001)
                #         #     print(i, '---', tt.now(5))
                #         # 1 / 60
                dx = dy = 0

            elif event_type == 'WheelEvent':
                # ms.position = event_i['x'], event_i['y']
                pass
            else:
                if need_press_alt:
                    kk.release('alt')
                raise TypeError(f"*** event_type[{event_type}]错误!")

        if need_press_alt:
            kk.release('alt')

    def remove_all_recorded_states(self):
        remove_path(self.save_dir, keep_external_folder=True)

    def save_record(self, file_name: str = ""):
        os.makedirs(self.save_dir, exist_ok=True)

        data = json.dumps(self.recorded_mouse_events, indent=4)

        timestamp = tt.get_current_beijing_time_str(tt.common_date_time_formats.ms_int[-8:])

        file_name = file_name if file_name else f'record__{timestamp}.json'

        file_path = os.path.join(self.save_dir, file_name)
        with open(file_path, 'w+') as f:
            f.write(data)

        print(f'{self.__class__.__name__} --- save_record to --- {file_path}')
        return file_path

    def load_record(self, file_name):
        file_path = os.path.join(self.save_dir, file_name)
        with open(file_path, 'r') as f:
            self.recorded_mouse_events: list = json.load(f)
        e = self.recorded_mouse_events[0]
        assert e['recorder_type'] == self.__class__.__name__


def run(abs_mouse_recorder=None, save_file_name: (None, str) = "", play=False, remove_else_recoded=False, tt=None, play_target=False,
        need_press_alt=False, hotkey__until='alt + x'):
    try:
        if abs_mouse_recorder is None:
            tt = tt if tt else Time()
            abs_mouse_recorder = AbsMouseRecorder(tt=tt)
        else:
            tt = abs_mouse_recorder.tt

        abs_mouse_recorder: AbsMouseRecorder

        if play_target is True:
            play_target = save_file_name
            play = True

        if play_target:
            abs_mouse_recorder.load_record(play_target)
        else:
            abs_mouse_recorder.record(until=hotkey__until)
            print('abs_mouse_recorder.record --- now:', tt.now(),
                  ', len recorded_mouse_events ---', len(abs_mouse_recorder.recorded_mouse_events))

            if remove_else_recoded:
                abs_mouse_recorder.remove_all_recorded_states()

            if save_file_name:
                abs_mouse_recorder.save_record(save_file_name)

        if play:
            tt.sleep(1)
            print(f'====== start abs_mouse_recorder.play, tt.now: {tt.now()}')
            beep(400)
            abs_mouse_recorder.play(speed=1, need_press_alt=need_press_alt)

    except Exception as e:
        beep(400, 0.3)
        beep(600, 0.3)
        beep(800, 0.3)
        raise e


def play_record(target_file_name):
    run(save_file_name=target_file_name, play=True, play_target=True, need_press_alt=True)


def main():
    # need_press_alt = False
    need_press_alt = True
    hotkey__start = 'alt + z'
    hotkey__until = 'alt + x'
    save_file_name = "传送到蒙德"

    print(f'--- wait hotkey__start[{hotkey__start}]')
    keyboard.wait(hotkey__start)
    tt.sleep(0.5)
    print(f'--- start abs_mouse_recorder.record, stop until press hotkey__until [{hotkey__until}]')

    # run(save_file_name=save_file_name, play=True, play_target=False, need_press_alt=need_press_alt, hotkey__until=hotkey__until)
    run(save_file_name=save_file_name, play=True, play_target=True, need_press_alt=need_press_alt, hotkey__until=hotkey__until)


if __name__ == '__main__':
    main()
